package vip.xiaonuo.app.modular.auth.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.PhoneUtil;
import cn.hutool.core.util.StrUtil;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import vip.xiaonuo.app.core.consts.AppLoginUserRedisConstant;
import vip.xiaonuo.app.core.consts.AppConstant;
import vip.xiaonuo.app.core.exception.AppUserAuthException;
import vip.xiaonuo.app.core.exception.enums.AppUserAuthExceptionEnum;
import vip.xiaonuo.app.core.tx.TxIm;
import vip.xiaonuo.app.modular.appuser.entity.AppUser;
import vip.xiaonuo.app.modular.appuser.service.AppUserCodeService;
import vip.xiaonuo.app.modular.appuser.service.AppUserService;
import vip.xiaonuo.app.modular.auth.factory.AppLoginUserFactory;
import vip.xiaonuo.app.modular.auth.service.AppAuthService;
import vip.xiaonuo.core.consts.SymbolConstant;
import vip.xiaonuo.core.context.constant.ConstantContextHolder;
import vip.xiaonuo.core.exception.AuthException;

import vip.xiaonuo.core.exception.ServiceException;
import vip.xiaonuo.core.exception.enums.AuthExceptionEnum;
import vip.xiaonuo.core.exception.enums.ParamException;
import vip.xiaonuo.core.exception.enums.ServerExceptionEnum;
import vip.xiaonuo.core.pojo.login.AppLoginUser;
import vip.xiaonuo.core.util.HttpServletUtil;
import vip.xiaonuo.core.util.IpAddressUtil;

import vip.xiaonuo.sys.core.cache.RedisCache;
import vip.xiaonuo.sys.core.jwt.JwtPayLoad;
import vip.xiaonuo.sys.core.jwt.JwtTokenUtil;
import vip.xiaonuo.sys.modular.auth.service.AuthService;
import vip.xiaonuo.sys.modular.sms.enums.SmsSendSourceEnum;
import vip.xiaonuo.sys.modular.sms.enums.SmsVerifyEnum;
import vip.xiaonuo.sys.modular.sms.param.SysSmsVerifyParam;
import vip.xiaonuo.sys.modular.sms.service.SmsSenderService;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>Title : </p>
 * <p>Description : </p>
 * <p>DevelopTools : IDEA</p>
 * <p>DevelopSystem : Windows11</p>
 *
 * @Author : zxw
 * @Date 2023/9/11 15:43
 */
@Service
public class AppAuthServiceImpl implements AppAuthService {

    @Resource
    private AppUserService appUserService;
    @Resource
    private AppUserCodeService appUserCodeService;
    @Resource
    private SmsSenderService smsSenderService;
    @Resource
    private RedisCache redisCache;
    @Resource
    private AuthService authService;


    @Override
    @Transactional
    public String initLogin(String loginName, String password, String smsCode) {
        /**
         * 1.需要判断是密码登录 还是短信验证码
         * 2.账号密码登录: 首先需要判断是否存在此账号,再判断密码是否正确
         * 3.短信验证码：首先需要判断是否存在该用户  存在执行登录操作   不存在执行自动注册
         */
        //号码位数提示
        if (!PhoneUtil.isMobile(loginName)) {
            throw new AppUserAuthException(AppUserAuthExceptionEnum.PHONE_ERROR);
        }
        //登录方式提示
        if (ObjectUtil.isAllNotEmpty(password, smsCode)) {
            throw new AppUserAuthException(AppUserAuthExceptionEnum.LOGIN_TYPE);
        }
        // 根据账号获取用户
        AppUser appUser = appUserService.getUserByAccount(loginName);

        //1.密码登录
        if (ObjectUtil.isNotEmpty(password)){
            //a.账号不存在
            if (ObjectUtil.isEmpty(appUser)){
                throw new AppUserAuthException(AppUserAuthExceptionEnum.ACCOUNT_NOT_EXITS);
            }
            //b.密码错误
            if (ObjectUtil.notEqual(password, appUser.getPassWord())){
                throw new AppUserAuthException(AppUserAuthExceptionEnum.PWD_ERROR);
            }
        }

        //2.短信登录
        if (ObjectUtil.isNotEmpty(smsCode)){
            //a.校验短信是否正确
            this.checkSmCode(loginName, smsCode);
            //b.账号不存在， 则注册
            if (ObjectUtil.isEmpty(appUser)){
                this.initRegister(loginName);
                //重新获取用户信息
                appUser = appUserService.getUserByAccount(loginName);
            }
        }

        //账号被封
        if (ObjectUtil.equal(AppConstant.USER_STATUS_ONE, appUser.getStatus())){
            throw new AppUserAuthException(AppUserAuthExceptionEnum.ACCOUNT_FREEZE_ERROR);
        }
        return this.finalLogin(appUser);
    }

    /**
     *  校验短信是否正确
     * @param loginName
     * @param smsCode
     * @return
     */
    private void checkSmCode(String loginName, String smsCode) {
        SysSmsVerifyParam param = new SysSmsVerifyParam();
        param.setPhoneNumbers(loginName);
        param.setSmsSendSourceEnum(SmsSendSourceEnum.APP);
        param.setCode(smsCode);
        param.setTemplateCode(ConstantContextHolder.getSmsBaoConfigs().getTemplateCode());
        SmsVerifyEnum smsVerifyEnum = smsSenderService.verifyShortMessage(param);
        if (ObjectUtil.notEqual(SmsVerifyEnum.SUCCESS.getCode(), smsVerifyEnum.getCode())){
            throw new ParamException(smsVerifyEnum.getCode(), smsVerifyEnum.getMessage());
        }
    }


    @Override
    public String finalLogin(AppUser appUser) {
        //构造AppLoginUser
        AppLoginUser appLoginUser = genSysLoginCustomer(appUser);
        //构造jwtPayLoad
        JwtPayLoad jwtPayLoad = new JwtPayLoad(appLoginUser.getId(), appUser.getLoginName());
        //生成token
        String token = JwtTokenUtil.generateToken(jwtPayLoad);
        //缓存token与登录用户信息
        this.cacheLoginUser(jwtPayLoad, appLoginUser);
        //更新用户登录信息 设置最后登录ip和时间
        this.recordLastLogin(appUser.getId());
        //登录成功，设置springSecurityContext上下文信息
        this.setSpringSecurityContextAuthentication(appLoginUser);
        //单点登录
        Boolean enableSingleLogin = ConstantContextHolder.getEnableSingleLogin();
        if (enableSingleLogin) {
            this.enableSingleLogin(jwtPayLoad, appUser);
        }
        //返回token
        return token;
    }

    private void enableSingleLogin(JwtPayLoad jwtPayLoad, AppUser appUser) {
        //查询前缀为AppLoginUserRedisConstant.LOGIN_USER_ 全部数据
        Collection<String> keys = redisCache.keys(AppLoginUserRedisConstant.APP_LOGIN_USER_ + SymbolConstant.ASTERISK);
        //不为空
        if (keys != null) {
            // 去掉缓存key的AppLoginUserRedisConstant prefix前缀 , 得到Uuid
            Set<String> allKeys =
                    keys.stream().map(key -> StrUtil.removePrefix(key, AppLoginUserRedisConstant.APP_LOGIN_USER_)).collect(Collectors.toSet());
            //通过去除前缀得到jwtPayLoad生成的值  查询出全部用户数据  <k v>
            HashMap<String, AppLoginUser> allLoginUsers = new HashMap<>();
            for (String key : allKeys) {
                allLoginUsers.put(key, redisCache.getCache(AppLoginUserRedisConstant.APP_LOGIN_USER_ + key));
            }
            //循环操作 allLoginUsers
            for (Map.Entry<String, AppLoginUser> loginUserEntry : allLoginUsers.entrySet()) {
                //Uuid
                String loginUserKey = loginUserEntry.getKey();
                //AppLoginUser 实体参数
                AppLoginUser loginUser = loginUserEntry.getValue();
                //如果账号名称相同，并且redis缓存key和刚刚生成的用户的Uuid不一样，则清除以前登录的
                if (loginUser.getLoginName().equals(appUser.getLoginName())
                        && !loginUserKey.equals(jwtPayLoad.getUuid())) {
                    //获取缓存的用户
                    Object cacheObject = redisCache.getCache(AppLoginUserRedisConstant.APP_LOGIN_USER_ + loginUserKey);
                    //如果缓存的用户存在，清除会话，否则表示该会话信息已失效，不执行任何操作
                    if (ObjectUtil.isNotEmpty(cacheObject)) {
                        //清除登录会话
                        redisCache.delete(AppLoginUserRedisConstant.APP_LOGIN_USER_ + loginUserKey);
                        //创建退出登录日志

                    }
                }
            }
        }
    }


    @Override
    public AppLoginUser genSysLoginCustomer(AppUser appUser) {
        AppLoginUser appLoginUser = new AppLoginUser();
        BeanUtil.copyProperties(appUser, appLoginUser);
        //填充登录用户信息(包含用户设置  用户额外信息 权限数据范围)
        AppLoginUserFactory.fillAppLoginUserInfo(appLoginUser);
        //返回数据
        return appLoginUser;
    }


    //缓存token 与 登录用户信息
    private void cacheLoginUser(JwtPayLoad jwtPayLoad, AppLoginUser appLoginUser) {
        //取出uuid
        String redisLoginUserKey = jwtPayLoad.getUuid();
        //缓存redis
        // 前缀常量 拼接 jwtPayLoad的 Uuid 作为 Key  Values:取SysLoginUser数据  设置Session失效时间  时间设置为 秒
        redisCache.setCache(
                AppLoginUserRedisConstant.APP_LOGIN_USER_ + redisLoginUserKey,
                appLoginUser,
                ConstantContextHolder.getSessionTokenExpireSec(),
                TimeUnit.SECONDS);
    }

    /**
     * 记录最后登入时间
     *
     * @param id 用户id
     */
    private void recordLastLogin(Long id) {
        //获取ip地址
        String ipAddr = IpAddressUtil.getIp(HttpServletUtil.getRequest());
        // 自己写insert语句防止mp自动更新修改人和修改时间
        appUserService.recordLastLogin(id, ipAddr, DateTime.now());
    }


    @Override
    public void setSpringSecurityContextAuthentication(AppLoginUser appLoginUser) {
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                new UsernamePasswordAuthenticationToken(
                        appLoginUser,
                        null,
                        null);
        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
    }

    @Override
    public AppLoginUser getAppLoginUserByToken(String token) {
        //校验token
        authService.checkToken(token);

        //根据token获取JwtPayLoad部分
        JwtPayLoad jwtPayLoad = JwtTokenUtil.getJwtPayLoad(token);

        //从redis缓存中获取登录用户
        Object cacheObject = redisCache.getCache(AppLoginUserRedisConstant.APP_LOGIN_USER_ + jwtPayLoad.getUuid());

        //用户不存在则表示登录已过期
        if (ObjectUtil.isEmpty(cacheObject)) {
            throw new AuthException(AppUserAuthExceptionEnum.LOGIN_EXPIRED);
        }

        //转换成登录用户
        AppLoginUser appLoginUser = (AppLoginUser) cacheObject;

        //用户存在, 无痛刷新缓存，在登录过期前活动的用户自动刷新缓存时间
        this.cacheLoginUser(jwtPayLoad, appLoginUser);

        return appLoginUser;
    }


    @Override
    @Transactional
    public boolean initRegister(String loginName) {
        AppUser appUser = new AppUser();
        //设置账号
        appUser.setLoginName(loginName);
        //设置昵称
        appUser.setNick("user_" + StrUtil.sub(loginName, StrUtil.length(loginName), -4));
        //用户唯一识别码生成
        Integer appUserCode = appUserCodeService.createAppUserCode(loginName);
        //用户唯一标识 9位数
        appUser.setUserCode("uCode" + appUserCode);
        //腾讯IM标识  9位数
        appUser.setTxCode("txCode" + appUserCode);
        //设置默认账号状态
        appUser.setStatus(AppConstant.USER_STATUS_ZERO);
        appUser.setPassWord(ConstantContextHolder.getDefaultPassWord());
        //保存
        boolean register = appUserService.register(appUser);
        if (register) {
            //腾讯IM注册
            TxIm.accountImport(appUser.getTxCode(), appUser.getNick(), null);
        }else{
            throw new AppUserAuthException(AuthExceptionEnum.REGISTER_ERROR);
        }
        return register;
    }

    @Override
    public void logout() {
        HttpServletRequest request = HttpServletUtil.getRequest();

        if (ObjectUtil.isNotNull(request)) {

            //获取token
            String token = authService.getTokenFromRequest(request);

            //如果token为空直接返回
            if (ObjectUtil.isEmpty(token)) {
                return;
            }

            //校验token，错误则抛异常，待确定
            authService.checkToken(token);

            //根据token获取JwtPayLoad部分
            JwtPayLoad jwtPayLoad = JwtTokenUtil.getJwtPayLoad(token);

            //获取缓存的key
            String loginUserCacheKey = jwtPayLoad.getUuid();
            this.clearUser(loginUserCacheKey, jwtPayLoad.getAccount());

        } else {
            throw new ServiceException(ServerExceptionEnum.REQUEST_EMPTY);
        }

    }

    /**
     * 根据key清空登陆信息
     */
    private void clearUser(String loginUserKey, String account) {
        //获取缓存的用户
        Object cacheObject = redisCache.getCache(AppLoginUserRedisConstant.APP_LOGIN_USER_ + loginUserKey);
        //如果缓存的用户存在，清除会话，否则表示该会话信息已失效，不执行任何操作
        if (ObjectUtil.isNotEmpty(cacheObject)) {
            //清除登录会话
            redisCache.delete(AppLoginUserRedisConstant.APP_LOGIN_USER_ + loginUserKey);

        }
    }

}
